package risoe.syslab.fhnew.controller;

/*
 * Copyright (c) 2012-2014, Technical University of Denmark (DTU)
 * All rights reserved.
 * 
 * The Flexhouse 2.0 platform ("this software") is licensed under the
 * BSD 3-clause license.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *   - Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *   - Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 *   - Neither the name of DTU nor the names of its contributors may be used to
 *     endorse or promote products derived from this software without specific
 *     prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE TECHNICAL UNIVERSITY OF DENMARK BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

import java.rmi.NotBoundException;
import java.rmi.RemoteException;

import java.io.File;
import java.io.IOException;
import java.net.ServerSocket;
import py4j.GatewayServer;
import risoe.syslab.fhnew.gui.VisualizationEvent;
import risoe.syslab.fhnew.hw.Actuator;
import risoe.syslab.fhnew.hw.Sensor;
import risoe.syslab.model.CompositeString;
import risoe.syslab.model.flexhouse.ControllerParameter;

/**
 * This class facilitates communication with controllers written in Python
 * @author M. Svendsen
 */
public class PythonPluginController{
	private String mName;
	private String mOwner;
	private String mConfigFile;
	private PythonProxy mPythonProxy = null;

	// This must be public in order for the python module to establish communication
	public PythonController mPythonController;

	// Used for queueing the initialStateReceived call
	private Actuator[] mActuators;

	/**
	 * The interface that the Python module exposes to Java
	 */
	public interface PythonProxy {
		public void sensorUpdatedInternal(Sensor s);
		public void actuatorUpdatedInternal(Actuator a);
		public void initialStateInternal(Actuator[] actuators);
		public void controllerParameterUpdatedInternal(ControllerParameter parameter,
				CompositeString value);
		public void onVisualizationEventInternal(VisualizationEvent event);
		public void getControllerConfigInternal(ControllerConfig config);
		public String toString();
		public void printString(String string);
	}

	static {
		GatewayServer.turnLoggingOff();
		//GatewayServer.turnAllLoggingOn();
	}

	/**
	 * This method is called from the Python class PythonPluginController. Never call this from anywhere else
	 */
	public static void main(String[] args) throws RemoteException, InstantiationException, NotBoundException {
		if (args.length == 0) {
			System.out.println("Too few arguments");
			System.exit(1);
		}
		String name = args[0];
		String owner = args[1];

		// In order to start the python callback server on an ephemeral port,
		// the ServerSocket constructor with 0 as argument is used.
		int proxyport = 0;
		try {
			ServerSocket serverSocket = new ServerSocket(0);
			proxyport = serverSocket.getLocalPort();
			serverSocket.close();
		} catch (IOException e) {}

		// Start up the gatewayserver using ephemeral ports
		String configFile = args.length > 2? args[2]: null;
		GatewayServer gatewayServer = new GatewayServer(new PythonPluginController(name, owner, configFile), 0, proxyport, 0, 0, null);
		gatewayServer.start();

		// Print out the listening port so that the python client can discover it
		int listening_port = gatewayServer.getListeningPort();
		System.out.println("" + listening_port);
		int proxy_port = gatewayServer.getPythonPort();
		System.out.println("" + proxy_port);

		// Print out info for user to read
		System.out.println("Java-Python PluginController gateway started on port " +
				listening_port+", with python proxy on port: "+proxy_port+
				" using config file: "+configFile);
	}

	public PythonPluginController(String name, String owner, String cfgfile){
		mName = name;
		mOwner = owner;
		mConfigFile = cfgfile;
	}

	/**
	 * This function is called by the Python module once everything is properly initialized.
	 * Once this method has been called, the link between Java and Python is active
	 * @throws NotBoundException
	 * @throws InstantiationException
	 * @throws RemoteException
	 */
	public void setPythonProxy(PythonProxy proxy) throws RemoteException, InstantiationException, NotBoundException{
		mPythonProxy = proxy;
		mPythonController = new PythonController(mName, mOwner, mConfigFile);

		// Call queued initialStateReceived method
		mPythonProxy.initialStateInternal(mActuators);
	}


	/**
	 * The internal class that acts as the actual PluginController
	 */
	private class PythonController extends PluginController{

		/**
		 * Called remotely via the main method by python
		 */
		public PythonController(String name, String owner, String cfgfile)
				throws RemoteException, InstantiationException, NotBoundException {
			super(name, owner, cfgfile);
		}

		@Override
		public ControllerConfig supplyControllerConfig(ControllerConfig config) {
			mPythonProxy.getControllerConfigInternal(config);
			return config;
		}

		/**
		 * Forwarding of calls through proxy
		 */
		@Override
		public void initialStateReceived(Actuator[] actuators) {
			mActuators = actuators;
		}

		@Override
		public void actuatorUpdated(Actuator a) {
			mPythonProxy.actuatorUpdatedInternal(a);
		}

		@Override
		public void controllerParameterUpdated(ControllerParameter parameter,
				CompositeString value) {
			mPythonProxy.controllerParameterUpdatedInternal(parameter, value);
		}

		@Override
		public void onVisualizationEvent(VisualizationEvent event) {
			mPythonProxy.onVisualizationEventInternal(event);
		}

		@Override
		public void sensorUpdated(Sensor s) {
			mPythonProxy.sensorUpdatedInternal(s);
		}
	}
}
